package org.paic.vertx.httpwrapper;

import groovy.lang.Closure;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vertx.groovy.core.MultiMap;
import org.vertx.groovy.core.http.HttpServerRequest;
import org.vertx.groovy.core.http.HttpServerResponse;

public class ChainHandler {
	public final static String PRE = "pre";
	public final static String NEXT = "next";

	private ErrorCaller errorHandler = null;
	private Caller noMatchHandler = null;

	private Logger log = LoggerFactory.getLogger(ChainHandler.class);

	private final List<Interceptor> interceptorList = new ArrayList<Interceptor>();
	private final List<Router> routerList = new ArrayList<Router>();

	public ChainHandler setErrorHandler(ErrorCaller closure) {
		errorHandler = closure;
		return this;
	}

	public ChainHandler setNoMatchHandler(Caller closure) {
		noMatchHandler = closure;
		return this;
	}

	private Router getOne(String method, String urlPat) {
		for (Router one : routerList) {
			if (method.equals(one.method) && urlPat.equals(one.urlPat))
				return one;
		}

		return null;
	}

	public ChainHandler addRouter(String method, String urlPat, Caller handler) {
		// no repeat, replace instead
		Router one = getOne(method, urlPat);
		if (one != null) {
			one.handler = handler;
			return this;
		}

		Matcher m = Pattern.compile(":([A-Za-z][A-Za-z0-9_]*)").matcher(urlPat);
		StringBuffer sb = new StringBuffer();
		Set<String> groups = new HashSet<String>();
		while (m.find()) {
			String group = m.group().substring(1);
			if (groups.contains(group)) {
				throw new IllegalArgumentException("Cannot use identifier "
						+ group + " more than once in pattern string");
			}
			m.appendReplacement(sb, "(?<$1>[^\\/]+)");
			groups.add(group);
		}
		m.appendTail(sb);
		String regex = sb.toString();
		Router router = new Router(method, urlPat, Pattern.compile(regex),
				groups, handler);
		routerList.add(router);

		return this;
	}

	public ChainHandler addRouterRegEx(String method, String urlPat,
			Caller handler) {
		// no repeat, replace instead
		Router one = getOne(method, urlPat);
		if (one != null) {
			one.handler = handler;
			return this;
		}

		Router router = new Router(method, urlPat, Pattern.compile(urlPat),
				null, handler);
		routerList.add(router);
		return this;
	}

	// for short
	public ChainHandler get(String urlPat, Caller handler) {
		this.addRouter("get", urlPat, handler);
		return this;
	}

	public ChainHandler post(String urlPat, Caller handler) {
		this.addRouter("post", urlPat, handler);
		return this;
	}

	public ChainHandler getRegEx(String urlPat, Caller handler) {
		this.addRouterRegEx("get", urlPat, handler);
		return this;
	}

	public ChainHandler postRegEx(String urlPat, Caller handler) {
		this.addRouterRegEx("post", urlPat, handler);
		return this;
	}

	public ChainHandler addInterceptor(String name, String type, String urlPat,
			Caller handler) {
		boolean isPre = PRE.equals(type);
		if (!isPre && !NEXT.equals(type)) {
			throw new IllegalArgumentException("Type should be pre or next!");
		}

		// no repeat, replace instead
		for (Interceptor one : interceptorList) {
			if (name.equals(one.name) && type.equals(one.type)) {
				one.pattern = Pattern.compile(urlPat);
				one.handler = handler;
				return this;
			}
		}
		interceptorList.add(new Interceptor(name, type,
				Pattern.compile(urlPat), handler));
		return this;
	}

	private void route(HttpServerRequest req, HttpServerResponse resp) {
		String mehtod = req.getMethod();
		for (Router one : routerList) {
			if (!mehtod.equalsIgnoreCase(one.method))
				continue;

			Matcher m = one.pattern.matcher(req.getPath());
			if (!m.matches())
				continue;

			Map<String, String> params = new HashMap<String, String>(
					m.groupCount());
			if (one.groups != null) {
				for (String param : one.groups) {
					params.put(param, m.group(param));
				}
			} else {
				for (int i = 0; i < m.groupCount(); ++i) {
					params.put("param" + i, m.group(i + 1));
				}
			}

			MultiMap reqParams = req.getParams();
			Set<Entry<String, String>> entries = params.entrySet();
			for (Entry<String, String> entry : entries)
				reqParams.add(entry.getKey(), entry.getValue());

			one.handler.call(req, resp);
			return;
		}

		// no match
		if (this.noMatchHandler != null) {
			this.noMatchHandler.call(req, resp);
		} else {
			req.getResponse().setStatusCode(404).end();
		}
	}

	private boolean matchUrl(String url, Pattern pat) {
		return pat.matcher(url).matches();
	}

	private String genenateLogMsg(String name, String url, boolean isBegin) {
		return "Profile pre " + name + " for " + url + " "
				+ (isBegin ? " begin " : " enddd ")
				+ System.currentTimeMillis();
	}

	public void handle(HttpServerRequest req, HttpServerResponse resp) {
		try {
			String url = req.getPath();
			for (Interceptor one : interceptorList) {
				if (!PRE.equals(one.type) || !matchUrl(url, one.pattern))
					continue;

				log.debug(genenateLogMsg(one.name, url, true));
				boolean isOk = one.handle(req, resp);
				log.debug(genenateLogMsg(one.name, url, false));
				if (isOk)
					return;
			}

			log.debug(genenateLogMsg("main route", url, true));
			route(req, resp);
			log.debug(genenateLogMsg("main route", url, false));

			for (Interceptor one : interceptorList) {
				if (!NEXT.equals(one.type) || !matchUrl(url, one.pattern))
					continue;

				log.debug(genenateLogMsg(one.name, url, true));
				boolean isOk = one.handle(req, resp);
				log.debug(genenateLogMsg(one.name, url, false));
				if (isOk)
					return;
			}
		} catch (Exception ex) {
			log.error("Exception caught when hand request!", ex);
			if (errorHandler != null)
				errorHandler.call(req, resp, ex);
		}
	}

	// use groovy platform better
	@SuppressWarnings("rawtypes")
	public Closure asClosure() {
		return new Closure(this) {
			@Override
			public Object call(Object args) {
				HttpServerRequest req = (HttpServerRequest) args;
				HttpServerResponse resp = req.getResponse();
				handle(req, resp);
				return null;
			}
		};
	}

	// **** **** **** **** **** ****
	private static class Router {
		final String method;
		final String urlPat;
		final Pattern pattern;
		final Set<String> groups;
		Caller handler;

		public Router(String method, String urlPat, Pattern pattern,
				Set<String> groups, Caller handler) {
			this.method = method;
			this.urlPat = urlPat;
			this.pattern = pattern;
			this.groups = groups;
			this.handler = handler;
		}
	}

	private static class Interceptor {
		final String name;
		final String type;
		Pattern pattern;
		Caller handler;

		public Interceptor(String name, String type, Pattern pattern,
				Caller handler) {
			this.name = name;
			this.type = type;
			this.pattern = pattern;
			this.handler = handler;
		}

		// true means break and return
		public Boolean handle(HttpServerRequest req, HttpServerResponse resp) {
			return (Boolean) handler.call(req, resp);
		}
	}
}
